1
Transición hacia Gráficos de Alto Rendimiento
AI020Lesson 8
00:00

En gráficos por computadora, distinguimos entre Vectorial y Mapa de bits gráficos. Los gráficos vectoriales (como SVG) describen imágenes mediante formas lógicas; cada elemento es un objeto persistente en el DOM. Por el contrario, los gráficos de mapa de bits (como HTML5 Canvas) trabajan con mapas de puntos coloreados.

1. La Transición hacia Canvas

Aunque SVG es más fácil de estilizar mediante CSS, el navegador debe rastrear cada nodo. Para necesidades de alto rendimiento, como juegos con miles de partes móviles, la API de Canvas es superior. Proporciona un único elemento del DOM que encapsula una superficie de dibujo —esencialmente una "página en blanco".

2. El Contexto de Dibujo

El <canvas> elemento es una "caja negra" hasta que inicializamos su contexto. Los métodos de este objeto proporcionan la interfaz real de dibujo, desacoplando el elemento de visualización de la lógica de renderizado.

var contexto = lienzo.getContext("2d");

3. Conciencia sobre Espacios de Nombres

En gráficos basados en XML como SVG, el xmlns="http://www.w3.org/2000/svg" atributo es crítico. Indica al navegador que cambie del análisis estándar de HTML al esquema gráfico específico, permitiendo que las etiquetas de forma sean reconocidas como objetos interactivos.

main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
", "execution_steps": [ { "output": "Rendering SVG: Cyan circle (updated from red) and blue outlined square." }, { "output": "Rendering Canvas: Solid red rectangle drawn via pixel raster." } ] }; const executionSteps = (courseData && courseData.execution_steps) || []; // ── Machine Translation Data Hydration ── const domCode = document.getElementById('data-course-code'); if (domCode && courseData) { courseData.code = domCode.textContent; } const domExecs = document.querySelectorAll('#data-exec-output .data-exec'); domExecs.forEach(el => { const stepIdx = parseInt(el.getAttribute('data-step'), 10); if (!isNaN(stepIdx) && executionSteps[stepIdx]) { executionSteps[stepIdx].output = el.textContent.trim(); } }); const pageNumber = '1'; let visualMode = 'code'; let studyTimerInterval = null; let studyElapsedTime = 0; let studyLastStartTime = 0; let isStudyTimerRunning = false; let isSimRunning = false; let currentStep = 0; let timer = null; let animFrameId; let startTime = 0; // Global State let masterTimeline = null; let hybridInitialized = false; let totalTimelineSteps = 0; // tracks how many step labels the fallback timeline defines const els = { codeContainer: document.getElementById('code-container'), quizContainer: document.getElementById('quiz-container'), examContainer: document.getElementById('exam-container'), tabCode: document.getElementById('tab-code'), tabSim: document.getElementById('tab-sim'), tabQuiz: document.getElementById('tab-quiz'), tabExam: document.getElementById('tab-exam'), visControls: document.getElementById('vis-controls'), visStatusBar: document.getElementById('vis-status-bar'), code: document.getElementById('code-content'), visContainer: document.querySelector('.vis-container'), tipsBtn: document.getElementById('tips-btn'), drawer: document.getElementById('drawer-overlay'), timerDisplay: document.getElementById('study-timer'), timerVal: document.getElementById('timer-val'), consoleOutput: document.getElementById('console-output'), runBtn: document.getElementById('run-btn') }; // --- HELPERS --- /** * [通用渲染方法] General LaTeX Render Method * 封装 MathJax 渲染逻辑,可被多次调用 * @param {HTMLElement | Array} elements - 可选,指定渲染的元素或元素数组 */ function renderLaTeX(elements) { if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') { // 如果传入了具体元素,只渲染这些元素 if (elements) { //确保 elements 是数组形式 const elArray = Array.isArray(elements) ? elements : [elements]; MathJax.typesetPromise(elArray).catch(err => console.warn('MathJax specific render error:', err)); } else { // 否则渲染全页 MathJax.typesetPromise().catch(err => console.warn('MathJax global render error:', err)); } } } // Syntax Highlighter adapted for Python keywords function renderCode(codeStr) { return codeStr.split('\n').map((line, i) => { const lineNum = i + 1; let htmlLine = ''; const regex = /((?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'))|(#.*)|(\b(?:def|class|if|else|elif|while|for|in|return|import|from|as|try|except|finally|with|lambda|pass|break|continue)\b)|(\b(?:print|len|range|enumerate|zip|map|filter|set|list|dict|int|str|float|sum|max|min|append|pop)\b)|(\b(?:True|False|None|[0-9]+)\b)|(\+|-|\*|\/|=|<|>|!|%|\[|\]|\{|\}|\(|\))/g; let lastIndex = 0; let match; while ((match = regex.exec(line)) !== null) { const textBefore = line.slice(lastIndex, match.index); htmlLine += escapeHtml(textBefore); const [fullMatch, str, com, kw, fn, num, op] = match; if (str) htmlLine += `${escapeHtml(str)}`; else if (com) htmlLine += `${escapeHtml(com)}`; else if (kw) htmlLine += `${escapeHtml(kw)}`; else if (fn) htmlLine += `${escapeHtml(fn)}`; else if (num) htmlLine += `${escapeHtml(num)}`; else if (op) htmlLine += `${escapeHtml(op)}`; lastIndex = regex.lastIndex; } htmlLine += escapeHtml(line.slice(lastIndex)); return `
${lineNum}
${htmlLine}
`; }).join(''); } function escapeHtml(text) { if (!text) return ''; return text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // Helper to clear all highlights // Helper to clear all highlights function clearHighlights() { const lines = document.querySelectorAll('.code-line'); lines.forEach(l => l.classList.remove('executing')); } // --- INTERACTION LOGIC --- // NEW: Advanced Simulation with Line Highlighting window.runSimulatedCode = async function () { if (isSimRunning) return; const term = els.consoleOutput; if (!term) return; isSimRunning = true; if (els.runBtn) els.runBtn.disabled = true; try { // Initial Term State term.innerHTML = `
${courseData.run_cmd || '> python3 main.py'}
`; const wait = (ms) => new Promise(r => setTimeout(r, ms)); await wait(400); // Print all outputs at once (no line-by-line highlight) for (let i = 0; i < executionSteps.length; i++) { const step = executionSteps[i]; if (step.output) { const div = document.createElement('div'); div.className = 'console-line'; div.innerText = step.output; term.appendChild(div); } } // Finish const cursor = document.createElement('div'); cursor.className = 'console-line'; cursor.innerHTML = `> `; term.appendChild(cursor); term.scrollTop = term.scrollHeight; } catch (err) { console.error("Simulation error", err); } finally { isSimRunning = false; if (els.runBtn) els.runBtn.disabled = false; } }; // Copy Code Function window.copyCode = function () { if (navigator.clipboard) { navigator.clipboard.writeText(courseData.code).then(() => { const btn = document.querySelector('.ide-btn[onclick="copyCode()"]'); if (btn) { const parent = btn.parentElement; const originalHtml = parent.innerHTML; parent.innerHTML = ` Copied!`; lucide.createIcons(); setTimeout(() => { parent.innerHTML = originalHtml; lucide.createIcons(); }, 2000); } }); } }; // Challenge/Exam Toggle // Quiz Selection Logic function toggleFullScreen() { const elem = els.visContainer; if (elem) { if (!document.fullscreenElement) { elem.requestFullscreen().catch(err => { console.error(err); }); } else { document.exitFullscreen(); } } } const fsBtn = document.getElementById('fsBtn'); if (fsBtn) { document.addEventListener('fullscreenchange', () => { const isFull = !!document.fullscreenElement; fsBtn.innerHTML = isFull ? '' : ''; if (window.lucide) lucide.createIcons(); }); } // Quiz answer handler (reference-style from InnerPage General) window.checkAnswer = function(card, prefix, isCorrect) { const grid = card.closest('.quiz-options-grid'); if (grid.classList.contains('answered')) return; grid.classList.add('answered'); card.classList.add('selected', isCorrect ? 'correct' : 'incorrect'); const icon = document.createElement('i'); icon.setAttribute('data-lucide', isCorrect ? 'check-circle' : 'x-circle'); icon.style.flexShrink = '0'; card.appendChild(icon); if (window.lucide) lucide.createIcons(); const correctBox = document.getElementById(prefix + '-correct'); const incorrectBox = document.getElementById(prefix + '-incorrect'); if (isCorrect && correctBox) { correctBox.style.display = 'block'; } else if (!isCorrect && incorrectBox) { incorrectBox.style.display = 'block'; } }; // Exam solution toggle (reference-style from InnerPage General) window.toggleExamSolution = function(btn) { const ansDiv = btn.nextElementSibling; const isVis = ansDiv.classList.toggle('visible'); btn.innerHTML = isVis ? 'Hide Solution ' : 'Show Solution '; if (window.lucide) lucide.createIcons(); if (isVis) renderLaTeX(ansDiv); }; // --- APP LOGIC --- // Quiz card reset window.resetQuizCard = function(btn) { const card = btn.closest('.quiz-card'); if (!card) return; const grid = card.querySelector('.quiz-options-grid'); if (grid) { grid.classList.remove('answered'); grid.querySelectorAll('.quiz-opt-card').forEach(opt => { opt.classList.remove('selected', 'correct', 'incorrect'); const icon = opt.querySelector('i'); if (icon) icon.remove(); }); } card.querySelectorAll('.quiz-feedback-box').forEach(fb => { fb.style.display = 'none'; fb.classList.remove('visible'); }); }; function init() { try { if (courseData.filename && document.getElementById('code-filename-display')) { document.getElementById('code-filename-display').textContent = courseData.filename; } loadContent(); } catch (e) { console.error("Content loading failed", e); } try { initStudyTimer(); } catch (e) { console.error("Timer init failed", e); } if (courseData.visual && courseData.visual.simStructure && !courseData.visual.simSteps) { courseData.visual.simSteps = []; } if (els.tabCode) els.tabCode.onclick = () => setVisualMode('code'); if (els.tabSim) els.tabSim.onclick = () => setVisualMode('sim'); if (els.tabQuiz) els.tabQuiz.onclick = () => setVisualMode('quiz'); if (els.tabExam) els.tabExam.onclick = () => setVisualMode('exam'); if (document.getElementById('btn-play') && typeof togglePlay === 'function') document.getElementById('btn-play').onclick = togglePlay; if (document.getElementById('btn-next') && typeof step === 'function') document.getElementById('btn-next').onclick = () => step(1); if (document.getElementById('btn-prev') && typeof step === 'function') document.getElementById('btn-prev').onclick = () => step(-1); if (document.getElementById('btn-reset') && typeof resetSim === 'function') document.getElementById('btn-reset').onclick = resetSim; if (els.tipsBtn) els.tipsBtn.onclick = () => els.drawer.classList.add('active'); const closeDrawer = document.getElementById('close-drawer'); if (closeDrawer) closeDrawer.onclick = () => els.drawer.classList.remove('active'); if (els.drawer) els.drawer.onclick = (e) => { if (e.target === els.drawer) els.drawer.classList.remove('active'); }; } function initStudyTimer() { startStudyTimer(); if (els.timerDisplay) els.timerDisplay.onclick = toggleStudyTimer; } function toggleStudyTimer() { if (isStudyTimerRunning) { pauseStudyTimer(); } else { startStudyTimer(); } } function startStudyTimer() { if (isStudyTimerRunning) return; isStudyTimerRunning = true; studyLastStartTime = Date.now(); if (els.timerDisplay) els.timerDisplay.classList.remove('paused'); updateTimerDisplay(); studyTimerInterval = setInterval(updateTimerDisplay, 1000); } function pauseStudyTimer() { if (!isStudyTimerRunning) return; isStudyTimerRunning = false; studyElapsedTime += Date.now() - studyLastStartTime; clearInterval(studyTimerInterval); if (els.timerDisplay) els.timerDisplay.classList.add('paused'); updateTimerDisplay(); } function updateTimerDisplay() { let totalMs = studyElapsedTime; if (isStudyTimerRunning) totalMs += (Date.now() - studyLastStartTime); const totalSecs = Math.floor(totalMs / 1000); const m = Math.floor(totalSecs / 60).toString().padStart(2, '0'); const s = (totalSecs % 60).toString().padStart(2, '0'); if (els.timerVal) els.timerVal.innerText = `${m}:${s}`; } function loadContent() { renderLaTeX(); if (courseData.code && els.code) els.code.innerHTML = renderCode(courseData.code); setVisualMode(visualMode); } function setVisualMode(mode) { visualMode = mode; if (els.tabCode) els.tabCode.classList.toggle('active', mode === 'code'); if (els.tabSim) els.tabSim.classList.toggle('active', mode === 'sim'); if (els.tabQuiz) els.tabQuiz.classList.toggle('active', mode === 'quiz'); if (els.tabExam) els.tabExam.classList.toggle('active', mode === 'exam'); if (els.codeContainer) els.codeContainer.style.display = 'none'; if (els.quizContainer) els.quizContainer.style.display = 'none'; if (els.examContainer) els.examContainer.style.display = 'none'; if (els.canvas) els.canvas.style.display = 'none'; if (els.visControls) els.visControls.style.display = 'none'; if (els.visStatusBar) els.visStatusBar.style.display = 'none'; if (mode === 'code') { if (els.codeContainer) els.codeContainer.style.display = 'flex'; } else if (mode === 'quiz') { if (els.quizContainer) { els.quizContainer.style.display = 'block'; renderLaTeX(els.quizContainer); } } else if (mode === 'exam') { if (els.examContainer) { els.examContainer.style.display = 'block'; renderLaTeX(els.examContainer); } } } init(); window.addEventListener('resize', () => {});